package com.tfleo.mealssenger.service;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutionException;

import org.jivesoftware.smack.AccountManager;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.MessageTypeFilter;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
import org.json.JSONObject;

import com.tfleo.mealssenger.ChatActivity;
import com.tfleo.mealssenger.Main;
import com.tfleo.mealssenger.R;
import com.tfleo.mealssenger.object.User;
import com.tfleo.mealssenger.utility.NetworkTools;
import com.tfleo.mealssenger.utility.SecurityTools;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.IBinder;
import android.util.Log;

/**
 * Service that handles communication with chat server, using Smack library. 
 * This service will keep running until user intentionally stop it.
 * 
 * Most of the code is referred from Anton Lopyrev's
 * https://github.com/tokudu/AndroidPushNotificationsDemo
 * 
 * @author Theodore Felix Leo
 */
public class ChatService extends Service {
	public static final String TAG = "DemoPushService";
	public static final String CHAT_WITH = "ChatWith";
	public static final String MEALSSENGER_CHATRECEIVE = "MessageReceived";
	public static final String MEALSSENGER_CHATSEND = "MessageSent";
	public static final String MEALSSENGER_CHATSTATUS = "CheckStatus";
	public static final String MEALSSENGER_CHATROSTER = "CheckRoster";

	// Service actions
	private static final String ACTION_START = "start";
	private static final String ACTION_STOP = "stop";
	private static final String	ACTION_REGISTER = "register";
	private static final String	ACTION_KEEPALIVE = "keepAlive";
	private static final String	ACTION_RECONNECT = "reconnect";
	private static final String	ACTION_ADDFRIEND = "addFriend";
	private static final String	ACTION_APPROVEFRIEND = "approveFriend";	
	private static final String ACTION_GETSTATUS = "getStatus";

	// Preferences
	public static final String PREF_STARTED = "isStarted";
	public static final String PREF_RETRY = "retryInterval";

	// Notification title
	public static String NOTIF_TITLE = "Mealssenger";

	// Server info
	private static final String HOST = "122.248.235.173";
	private static final String PORT = "5222";
	private static final String JID = "@ip-10-130-59-102";
	private static final int JID_LENGTH = 17;

	private static final String CHAT_PROCESS = "com.tfleo.mealssenger:ChatProcess";

	// AlarmManager keep-alive interval
	private static final long KEEP_ALIVE_INTERVAL = 1000 * 60 * 60; //hourly
	// Retry intervals, when the connection is lost.
	private static final long INITIAL_RETRY_INTERVAL = 1000 * 10;
	private static final long MAXIMUM_RETRY_INTERVAL = 1000 * 60 * 30;

	private SharedPreferences mAppSettings;
	//private ConnectivityManager	mConnectionManager;
	private NotificationManager	mNotificationManager;

	private XMPPConnection mConnection;
	private String fromUser;

	// Notification messages stack
	private HashMap<String, String> mBufferedMessage;
	private String mCurrentId = "";
	private boolean mStarted;
	private long mStartTime;

	/**
	 * Static method to start service
	 * @param ctx Application context
	 */
	public static void actionStart(Context ctx) {
		Intent i = new Intent(ctx, ChatService.class);
		i.setAction(ACTION_START);
		ctx.startService(i);
	}

	/**
	 * Static method to stop service
	 * @param ctx Application context
	 */
	public static void actionStop(Context ctx) {
		Intent i = new Intent(ctx, ChatService.class);
		i.setAction(ACTION_STOP);
		ctx.startService(i);
	}

	/**
	 * Static method to send keep alive
	 * @param ctx Application context
	 */
	public static void actionPing(Context ctx) {
		Intent i = new Intent(ctx, ChatService.class);
		i.setAction(ACTION_KEEPALIVE);
		ctx.startService(i);
	}

	public static void actionRegister(Context ctx) {
		Intent i = new Intent(ctx, ChatService.class);
		i.setAction(ACTION_REGISTER);
		ctx.startService(i);
	}

	public static void actionAddFriend(Context ctx, String toUser) {
		Intent i = new Intent(ctx, ChatService.class);
		i.setAction(ACTION_ADDFRIEND);
		i.putExtra("toUser", toUser);
		ctx.startService(i);
	}

	public static void actionApproveFriend(Context ctx, String toUser) {
		Intent i = new Intent(ctx, ChatService.class);
		i.setAction(ACTION_APPROVEFRIEND);
		i.putExtra("toUser", toUser);
		ctx.startService(i);
	}

	public static void actionGetStatus(Context ctx, String ofUser) {
		Intent i = new Intent(ctx, ChatService.class);
		i.setAction(ACTION_GETSTATUS);
		i.putExtra("ofUser", ofUser);
		ctx.startService(i);
	}

	/*public static void actionGetRoster(Context ctx, String ofUser) {
		Intent i = new Intent(ctx, ChatService.class);
		i.setAction(ACTION_GETROSTER);
		i.putExtra("ofUser", ofUser);
		ctx.startService(i);
	}*/

	@Override
	public void onCreate() {
		super.onCreate();
		Log.i(TAG, "Creating service...");
		mStartTime = System.currentTimeMillis();
		mBufferedMessage = new HashMap<String, String>();

		mAppSettings = getSharedPreferences(TAG, MODE_PRIVATE);
		//mConnectionManager = (ConnectivityManager)getSystemService(CONNECTIVITY_SERVICE);
		mNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);

		// In case the service was reaped by the system
		handleCrashedService();
	}

	/**
	 * Handle necessary clean-up need in case the service has been destroyed
	 * by the system and then restarted.
	 * 
	 */
	private void handleCrashedService() {
		if (mAppSettings.getBoolean(PREF_STARTED, false)) {
			Log.i(TAG, "Handling crashed service");		
			stopKeepAlives(); 

			// Clean start
			start();
		}
	}

	@Override
	public void onDestroy() {
		Log.i(TAG, "Service destroyed (started = " + mStarted + ")");

		// Stop the services if it has been started
		if (mStarted == true) {
			stop();
		}
	}

	@Override
	public void onStart(Intent intent, int startId) {
		if(intent == null) {
			intent = new Intent();
			intent.setAction(ACTION_START);
			handleCrashedService();
			super.onStart(intent, startId);
			return;
		}
		
		super.onStart(intent, startId);
		Log.i(TAG, "Service started with intent: " + intent);


		if(!NetworkTools.isNetworkAvailable(this)) {
			return;
		}

		// Do an appropriate action based on the intent
		if (intent.getAction().equals(ACTION_STOP) == true) {
			stop();
			stopKeepAlives();
			stopSelf();
		} else if (intent.getAction().equals(ACTION_START) == true) {
			start();
		} else if (intent.getAction().equals(ACTION_KEEPALIVE) == true) {
			keepAlive();
		} else if (intent.getAction().equals(ACTION_RECONNECT) == true) {
			//if (isNetworkAvailable()) {
			reconnectIfNecessary();
			//}
		} else if (intent.getAction().equals(ACTION_REGISTER) == true) {
			register();
		} else if (intent.getAction().equals(ACTION_ADDFRIEND) == true) {
			addFriend(intent.getStringExtra("toUser"));
		} else if (intent.getAction().equals(ACTION_APPROVEFRIEND) == true) {
			approveFriend(intent.getStringExtra("toUser"));
		} else if (intent.getAction().equals(ACTION_GETSTATUS) == true) {
			getStatus(intent.getStringExtra("ofUser"));
		}
	}

	@Override
	public IBinder onBind(Intent intent) {
		return null;
	}

	private void setStarted(boolean started) {
		mAppSettings.edit().putBoolean(PREF_STARTED, started).commit();		
		mStarted = started;
	}

	/**
	 * Register user to chat server with user name and password stored.
	 */
	private synchronized void register() {
		Log.i(TAG, "Registering new chat account...");
		// Do nothing, if the service is already running
		if (mStarted == true) {
			Log.w(TAG, "ChatService is running but user is not registered yet!");
			//TODO: kill service and do necessary clean-up
			return;
		}

		// Create a connection
		ConnectionConfiguration connConfig =
				new ConnectionConfiguration(HOST, Integer.parseInt(PORT), "");
		mConnection = new XMPPConnection(connConfig);

		try {
			mConnection.connect();
			Log.i(TAG, "[XMPP] Connected to " + mConnection.getHost());
		} catch (XMPPException e) {
			Log.e(TAG, "[XMPP] Failed to connect to " + mConnection.getHost());
			Log.e(TAG, e.toString());
		}

		SharedPreferences credentials = getSharedPreferences(Main.USER_DATA, MODE_PRIVATE);
		String userName = credentials.getString(Main.CHAT_ID, "");
		try {
			String token = SecurityTools.decrypt(SecurityTools.MASTER_KEY, credentials.getString(Main.TOKEN, ""));
			AccountManager accountManager = new AccountManager(mConnection);

			HashMap<String, String> attributes = new HashMap<String, String>();
			attributes.put("name", userName);
			accountManager.createAccount(userName, token, attributes);
		} catch (XMPPException e) {
			Log.e(TAG, "[XMPP] Failed to register user: " + userName);
			Log.e(TAG, e.toString());
		} catch (Exception e) {
			Log.e(TAG, e.toString());
		}

		Log.i(TAG, "Successfully register user: " + userName);

		// Kill service
		mConnection.disconnect();
		cancelReconnect();
		mConnection = null;
		stop();
		stopSelf();
	}

	/**
	 * Initialize and start chatting service routine.
	 */
	private synchronized void start() {
		Log.i(TAG, "Starting service...");

		// Do nothing, if the service is already running.
		if (mStarted == true) {
			Log.w(TAG, "Attempt to start connection that is already active!");
			return;
		}

		// Setup XMPP connection
		connect();

		// Register a connectivity listener
		registerReceiver(mConnectivityChanged, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
		registerReceiver(mMessageSent, new IntentFilter(MEALSSENGER_CHATSEND));
	}

	/**
	 * Stop service and do necessary clean-up.
	 */
	private synchronized void stop() {
		// Do nothing if the service is not running.
		if (mStarted == false) {
			Log.w(TAG, "Attempt to stop inactive connection");
			return;
		}

		// Save stopped state in the preferences
		setStarted(false);

		// Remove the connectivity receiver
		unregisterReceiver(mConnectivityChanged);
		unregisterReceiver(mMessageSent);

		// Any existing reconnect timers should be removed, since we explicitly stopping the service.
		cancelReconnect();

		// Destroy the XMPP connection if there is one
		if (mConnection != null) {
			mConnection.disconnect();
			mConnection = null;
		}
	}

	/**
	 * Establish connection to XMPP server.
	 */
	private synchronized void connect() {		
		// Create XMPP connection
		ConnectionConfiguration connConfig =
				new ConnectionConfiguration(HOST, Integer.parseInt(PORT), "");
		mConnection = new XMPPConnection(connConfig);

		try {
			mConnection.connect();
			Log.i(TAG, "[XMPP] Connected to " + mConnection.getHost());
		} catch (XMPPException e) {
			Log.e(TAG, "[XMPP] Failed to connect to " + mConnection.getHost());
			Log.e(TAG, e.toString());
			if (NetworkTools.isNetworkAvailable(this)) {
				scheduleReconnect(mStartTime);
			}
		}

		// Login and set presence to available
		SharedPreferences credentials = getSharedPreferences(Main.USER_DATA, MODE_PRIVATE);
		String user = credentials.getString(Main.CHAT_ID, "");
		try {
			String token = SecurityTools.decrypt(SecurityTools.MASTER_KEY, credentials.getString(Main.TOKEN, ""));
			mConnection.login(user.toLowerCase(), token);
			Log.i(TAG, "[XMPP] Logged in as " + mConnection.getUser());

			Presence presence = new Presence(Presence.Type.available);
			mConnection.sendPacket(presence);
		} catch (XMPPException e) {
			Log.e(TAG, "[XMPP] Failed to log in as ");
			Log.e(TAG, e.toString());
		} catch (Exception e) {
			Log.e(TAG, e.toString());
		}

		// Add listener to chat message
		PacketFilter messageFilter = new MessageTypeFilter(Message.Type.chat);
		mConnection.addPacketListener(new PacketListener() {
			public void processPacket(Packet packet) {
				Message message = (Message) packet;
				if (message.getBody() != null) {
					String fromName = StringUtils.parseBareAddress(message.getFrom());
					// Extract chat id
					fromName = fromName.substring(0, fromName.length() - JID_LENGTH);
					Log.i(TAG, "[XMPP] Got text [" + message.getBody() + "] from [" + fromName + "]");

					boolean isForeground = false;
					// Check if ChatActivity is currently foreground
					try {
						isForeground = new ForegroundCheckTask().execute(getBaseContext()).get();
					} catch (InterruptedException e) {
						Log.e(TAG, e.toString());
					} catch (ExecutionException e) {
						Log.e(TAG, e.toString());
					}

					if(isForeground) {
						Log.i(TAG, "Chat is foreground");
						if(mCurrentId.compareTo(fromName) == 0) {
							mBufferedMessage.remove(fromName); // Remove buffered message 
							handleForegroundMessage(message.getBody());
						} else {
							handleBackgroundMessage(message.getBody(), fromName);
						}
					} else {
						handleBackgroundMessage(message.getBody(), fromName);
					}
				}
			}
		}, messageFilter);

		PacketFilter subscriptionFilter = new PacketFilter() {
			@Override
			public boolean accept(Packet packet) {
				fromUser = packet.getFrom();
				return packet.equals(Presence.Type.subscribe);
			}

		};
		// Add listener to subscription data
		mConnection.addPacketListener(new PacketListener() {
			public void processPacket(Packet packet) {
				Presence subscribed = new Presence(Presence.Type.subscribed);
				subscribed.setTo(fromUser);
				mConnection.sendPacket(subscribed);
			}
		}, subscriptionFilter);

		Log.i(TAG, "Checking roster...");
		Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.accept_all);
		Roster roster = mConnection.getRoster();
		roster.addRosterListener(new RosterListener() {
			@Override
			public void entriesAdded(Collection<String> addresses) {}
			@Override
			public void entriesDeleted(Collection<String> addresses) {}
			@Override
			public void entriesUpdated(Collection<String> addresses) {}
			@Override
			public void presenceChanged(Presence presence) {
				Log.i(TAG, "Presence changed: " + presence.getFrom() + " " + presence);
				Log.i(TAG, "Status: " + presence.getStatus());
			}
		});

		startKeepAlives();
		setStarted(true);
		Log.i(TAG, "Connected to XMPP server");
	}

	/**
	 * Keep connection alive by updating status within given interval.
	 */
	private synchronized void keepAlive() {
		if (mStarted == true && mConnection != null) {
			Presence presence = new Presence(Presence.Type.available);
			mConnection.sendPacket(presence);
		}
	}

	/**
	 * Add user as chat friend.
	 * 
	 * @param toUser
	 */
	private synchronized void addFriend(String toUser) {
		Log.i(TAG, "Adding " + toUser + " as friend");

		if (mStarted == true) {
			Log.w(TAG, "Attempt to start connection that is already active");

			Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.accept_all);
			Roster roster = mConnection.getRoster();

			try {
				roster.createEntry(toUser + JID, toUser, null);
			} catch (XMPPException e) {
				Log.e(TAG, e.toString());
			}
			return;
		} else {
			// Create a connection
			ConnectionConfiguration connConfig =
					new ConnectionConfiguration(HOST, Integer.parseInt(PORT), "");
			mConnection = new XMPPConnection(connConfig);

			try {
				mConnection.connect();
				Log.i(TAG, "[XMPP] Connected to " + mConnection.getHost());
			} catch (XMPPException e) {
				Log.e(TAG, "[XMPP] Failed to connect to " + mConnection.getHost());
				Log.e(TAG, e.toString());
			}

			SharedPreferences credentials = getSharedPreferences(Main.USER_DATA, MODE_PRIVATE);
			String user = credentials.getString(Main.CHAT_ID, "");
			try {
				String token = SecurityTools.decrypt(SecurityTools.MASTER_KEY, credentials.getString(Main.TOKEN, ""));
				user = user.replace(' ', '_');
				Log.i(TAG, user.toLowerCase() + " " + token);
				mConnection.login(user.toLowerCase(), token);
				Log.i(TAG, "[XMPP] Logged in as " + mConnection.getUser());
			} catch (XMPPException e) {
				Log.e(TAG, "[XMPP] Failed to log in as " + mConnection.getUser());
				Log.e(TAG, e.toString());
			} catch (Exception e) {
				Log.e(TAG, e.toString());
			}

			Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.accept_all);
			Roster roster = mConnection.getRoster();

			try {
				roster.createEntry(toUser + JID, toUser, null);
			} catch (XMPPException e) {
				Log.e(TAG, e.toString());
			}

			mConnection.disconnect();
			cancelReconnect();
			mConnection = null;
			stop();
			stopSelf();
		}
	}

	/**
	 * Approve user as chat friend.
	 * 
	 * @param fromUser
	 */
	private synchronized void approveFriend(final String fromUser) {
		Log.i(TAG, "Approving " + fromUser + " as friend");
		if (mStarted == true) {
			PacketFilter filter_sub = new PacketFilter() {
				@Override
				public boolean accept(Packet packet) {
					return packet.equals(Presence.Type.subscribe);
				}

			};

			mConnection.addPacketListener(new PacketListener() {
				public void processPacket(Packet packet) {
					Presence subscribed = new Presence(Presence.Type.subscribed);
					subscribed.setTo(fromUser + JID);
					mConnection.sendPacket(subscribed);
				}
			}, filter_sub);

			Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.accept_all);
			Roster roster = mConnection.getRoster();
			try {
				roster.createEntry(fromUser + JID, fromUser, null);
			} catch (XMPPException e) {
				e.printStackTrace();
			}

			return;
		} else {
			// Create a connection
			ConnectionConfiguration connConfig =
					new ConnectionConfiguration(HOST, Integer.parseInt(PORT), "");
			mConnection = new XMPPConnection(connConfig);

			try {
				mConnection.connect();
				Log.i(TAG, "[XMPP] Connected to " + mConnection.getHost());
			} catch (XMPPException ex) {
				Log.e(TAG, "[XMPP] Failed to connect to " + mConnection.getHost());
				Log.e(TAG, ex.toString());
			}

			try {
				SharedPreferences credentials = getSharedPreferences(Main.USER_DATA, MODE_PRIVATE);
				String user = credentials.getString(Main.CHAT_ID, "");
				String token = SecurityTools.decrypt(SecurityTools.MASTER_KEY, credentials.getString(Main.TOKEN, ""));

				mConnection.login(user, token);
				Log.i(TAG, "[XMPP] Logged in as " + mConnection.getUser());
			} catch (XMPPException e) {
				Log.e(TAG, "[XMPP] Failed to log in as " + mConnection.getUser());
				Log.e(TAG, e.toString());
			} catch (Exception e) {
				Log.e(TAG, e.toString());
			}

			PacketFilter subscriptionFilter = new PacketFilter() {
				@Override
				public boolean accept(Packet packet) {
					return packet.equals(Presence.Type.subscribe);
				}

			};
			mConnection.addPacketListener(new PacketListener() {
				public void processPacket(Packet packet) {
					Presence subscribed = new Presence(Presence.Type.subscribed);
					subscribed.setTo(fromUser + JID);
					mConnection.sendPacket(subscribed);
				}
			}, subscriptionFilter);

			Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.accept_all);
			Roster roster = mConnection.getRoster();
			try {
				roster.createEntry(fromUser + JID, fromUser, null);
			} catch (XMPPException e) {
				e.printStackTrace();
			}

			mConnection.disconnect();
			cancelReconnect();
			mConnection = null;
			stop();
			stopSelf();
		}
	}

	/**
	 * Get status of user and broadcast it back to UserProfileActivity.
	 * 
	 * @param ofUser
	 */
	private synchronized void getStatus(final String ofUser) {
		Log.i(TAG, "Getting user status...");

		if (mStarted == true) {
			Log.i(TAG, "Checking status of " + ofUser);
			Roster roster = mConnection.getRoster();
			Presence p = roster.getPresence(ofUser + JID);
			if(p == null) {
				Intent intent = new Intent(MEALSSENGER_CHATSTATUS);
				intent.putExtra("status", false);
				intent.putExtra("location", "");
				sendBroadcast(intent);
				return;
			}
			Intent intent = new Intent(MEALSSENGER_CHATSTATUS);
			intent.putExtra("status", p.isAvailable());
			intent.putExtra("location", p.getStatus());
			sendBroadcast(intent);
			return;
		} else {
			return;
		}
	}

	/**
	 * Routine to keep-alive service periodically using AlarmManager.
	 */
	private void startKeepAlives() {
		Intent i = new Intent();
		i.setClass(this, ChatService.class);
		i.setAction(ACTION_KEEPALIVE);
		PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
		AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);
		alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, 
				System.currentTimeMillis() + KEEP_ALIVE_INTERVAL,
				KEEP_ALIVE_INTERVAL, pi);
	}

	/**
	 * Routine to stop keep-alive service.
	 */
	private void stopKeepAlives() {
		Intent i = new Intent();
		i.setClass(this, ChatService.class);
		i.setAction(ACTION_KEEPALIVE);
		PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
		AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE);
		alarmMgr.cancel(pi);
	}

	/**
	 * Schedule reconnect if failed to connect.
	 * 
	 * @param startTime
	 */
	public void scheduleReconnect(long startTime) {
		// The last keep-alive interval
		long interval = mAppSettings.getLong(PREF_RETRY, INITIAL_RETRY_INTERVAL);

		// Calculate the elapsed time since the start
		long now = System.currentTimeMillis();
		long elapsed = now - startTime;


		// Set an appropriate interval based on the elapsed time since start 
		if (elapsed < interval) {
			interval = Math.min(interval * 4, MAXIMUM_RETRY_INTERVAL);
		} else {
			interval = INITIAL_RETRY_INTERVAL;
		}

		Log.i(TAG, "Rescheduling connection in " + interval + "ms.");

		// Save the new internval
		mAppSettings.edit().putLong(PREF_RETRY, interval).commit();

		// Schedule a reconnect using the alarm manager.
		Intent i = new Intent();
		i.setClass(this, ChatService.class);
		i.setAction(ACTION_RECONNECT);
		PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
		AlarmManager alarmMgr = (AlarmManager)getSystemService(ALARM_SERVICE);
		alarmMgr.set(AlarmManager.RTC_WAKEUP, now + interval, pi);
	}

	/**
	 * Cancel reconnect.
	 */
	public void cancelReconnect() {
		Intent i = new Intent();
		i.setClass(this, ChatService.class);
		i.setAction(ACTION_RECONNECT);
		PendingIntent pi = PendingIntent.getService(this, 0, i, 0);
		AlarmManager alarmMgr = (AlarmManager)getSystemService(ALARM_SERVICE);
		alarmMgr.cancel(pi);
	}

	/**
	 * Do reconnect routine if service is previously started
	 * but the is not connected.
	 */
	private synchronized void reconnectIfNecessary() {		
		if (mStarted == true && mConnection == null) {
			Log.i(TAG, "Reconnecting...");
			connect();
		}
	}

	/**
	 * Handle message received when ChatActivity is on foreground.
	 * 
	 * @param message
	 */
	private void handleForegroundMessage(String message) {
		Intent intent = new Intent(MEALSSENGER_CHATRECEIVE);
		intent.putExtra("message", message);
		sendBroadcast(intent);

	}

	/**
	 * Handle message received when ChatActivity is on background
	 * 
	 * @param message
	 * @param from
	 */
	private void handleBackgroundMessage(String message, String from) {
		//mOldText.put(Integer.parseInt(fromName), message);
		showNotification(message, from);
	}

	/**
	 * Show notification of new message
	 * 
	 * @param message
	 * @param from
	 */
	private void showNotification(String message, String from) {
		Notification n = new Notification();

		n.flags |= Notification.FLAG_SHOW_LIGHTS;
		n.flags |= Notification.FLAG_AUTO_CANCEL;

		n.defaults = Notification.DEFAULT_ALL;
		n.defaults |= Notification.DEFAULT_VIBRATE;

		n.icon = R.drawable.icon;
		n.when = System.currentTimeMillis();
		
		//String name = getName(Integer.parseInt("from"));
		String current = message;
		String old = mBufferedMessage.get(from);

		if(old == null || old.indexOf('#') != -1) { // no previously buffered message
			old = ""; 
		} else {
			//TODO: treat '|' as special character, prevent any usage in conversation
			message = message + '|' + old;
		}
		mBufferedMessage.put(from, message); //store old text

		Intent i = new Intent(getApplicationContext(), ChatActivity.class);
		i.putExtra("id", Integer.parseInt(from));
		//i.putExtra("name", name);
		i.putExtra("message", message);
		i.putExtra("from_notif", true);
		i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

		PendingIntent pi = PendingIntent.getActivity(this, Integer.parseInt(from),
				i, PendingIntent.FLAG_UPDATE_CURRENT);

		// Set notification display to newest one
		n.setLatestEventInfo(this, NOTIF_TITLE, current, pi);

		mNotificationManager.notify(Integer.parseInt(from), n);
	}

	/**
	 * Task to check whether ChatActivity (CHAT_PROCESS) is currently on foreground.
	 */
	class ForegroundCheckTask extends AsyncTask<Context, Void, Boolean> {

		@Override
		protected Boolean doInBackground(Context... params) {
			final Context context = params[0].getApplicationContext();
			return isAppOnForeground(context, CHAT_PROCESS);
		}

		/**
		 * Check if process is on foreground in current context.
		 * 
		 * @param context Application context
		 * @param process Name of process to be checked
		 * @return true if process is on foreground
		 */
		private boolean isAppOnForeground(Context context, String process) {
			ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
			List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
			if (appProcesses == null) {
				return false;
			}

			for (RunningAppProcessInfo appProcess : appProcesses) {
				Log.i(TAG, appProcess.processName);
				if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND && 
						appProcess.processName.equals(process)) {
					return true;
				}
			}
			return false;
		}
	}

	/**
	 * Listen to change in network connectivity and do necessary action.
	 */
	private BroadcastReceiver mConnectivityChanged = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			// Get network info
			NetworkInfo info = (NetworkInfo)intent.getParcelableExtra (ConnectivityManager.EXTRA_NETWORK_INFO);

			// Is there connectivity?
			boolean hasConnectivity = (info != null && info.isConnected()) ? true : false;

			Log.i(TAG, "Connectivity changed: Connected [" + hasConnectivity + "]");

			if (hasConnectivity) {
				reconnectIfNecessary();
			} else if (mConnection != null) {
				mConnection.disconnect();
				cancelReconnect();
				mConnection = null;
			}
		}
	};
	
	/**
	 * Listen to message from ChatActivty and forward it to chat server.
	 */
	private BroadcastReceiver mMessageSent = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			String message = intent.getStringExtra("message");
			String toUser = intent.getStringExtra("toUser");

			if(message == null) {
				mCurrentId = toUser;
				return;
			}
			Log.i(TAG, "[XMPP] Sending text [" + message + "] to [" + toUser + "]");
			Message msg = new Message(toUser, Message.Type.chat);
			msg.setBody(message);
			mConnection.sendPacket(msg);
		}
	};
	
	

}